package app.model.proxies
{
	import app.model.vos.ActionVO;
	import app.model.vos.StateVO;
	
	import com.adobe.serialization.json.JSON;
	
	import flare.vis.data.Data;
	import flare.vis.data.EdgeSprite;
	import flare.vis.data.NodeSprite;
	import flare.vis.data.Tree;
	
	import flash.events.Event;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	
	import org.puremvc.as3.multicore.patterns.proxy.Proxy;

	public class FlareDataProxy extends Proxy
	{
		public static const NAME:String = "FlareDataProxy";
		public static const FLARE_DATA_POPULATED:String = "flareData/populated";
		public static const DATA_FLUSHED:String = "flareData/dataFlushed";
		
		private var url:String;
		private var map:Object;
		private var stateMap:Array;
		private var actionMap:Array;
		public var autoUpdate:Boolean = true;;
		public function FlareDataProxy(url:String):void
		{
			this.url = url;
			super(NAME);
		}
		
		private function get source():Data{
			return Data( data );
		}
		
		public function addState( name:String, pkg:String ):Boolean{
			var d:StateVO;
			if(contains( name ))return false;
			if(!pkg){
				pkg = "root.";
			}else{
				pkg = "root." + pkg +".";
			}
			d = new StateVO();
			d.fullPath = pkg + name;
			stateMap.push( d );
			updateVisualisation();
			return true;
		}
		
		public function removeState( name:String, update:Boolean=true ):Boolean{
			var vo:StateVO,
				n:int;
			if(!contains( name ))return false;
			while( n < stateMap.length ){
				vo = stateMap[n];
				if(vo.name == name){
					stateMap.splice(n, 1);
					break;
				}
				n++;
			}
			if( stateMap.length == 0 ){
				sendNotification(DATA_FLUSHED);
			}else{
				updateVisualisation();
			}
			
			return true;
		}
		
		private function sendDataFlushed():void{
			sendNotification(DATA_FLUSHED);
		}
		
		public function addAction( fromName:String, toName:String, actionName:String ):Boolean{
			var f:StateVO,
				t:StateVO,
				action:ActionVO;
			if( !contains( fromName ) || !contains( toName ) )return false;
			
			f = getState( fromName );
			t = getState( toName );
			action = new ActionVO( actionName, t.fullPath )
			if( f.contains( action.name ) )return false;
			f.addAction( action );
			actionMap.push( action );
			updateVisualisation();
			return true;
		}
		
		public function deleteAction( fromName:String, actionName:String ):Boolean{
			var f:StateVO,
				action:ActionVO;
			if( !contains( fromName ) )return false;
			f = getState( fromName );
			if( !f.contains( actionName ) )return false;
			f.deleteAction( actionName );
			removeFromMap( actionMap, action );
			updateVisualisation();
			return true;
		}
		
		private function removeFromMap( m:Array, o:Object ):void{
			var i:int;
			i = m.indexOf( o );
			m.splice(i, 1);
		}
		
		public function update():void{
			setData( buildData(stateMap) );
		}
		
		public function flush():void{
			map = {};
			stateMap = [];
			actionMap = [];
			sendDataFlushed();
		}
		
		private function updateVisualisation():void{
			if(autoUpdate)update(  );
		}
		
		public function getState( name:String ):StateVO{
			var n:int,
				vo:StateVO;
			while( n < stateMap.length ){
				vo = stateMap[n];
				if(vo.name == name)return vo;
				n++;
			}
			return null;
		}
		
		
		public function contains( name:String ):Boolean{
			var n:int,
				vo:StateVO;
			while( n < stateMap.length ){
				vo = stateMap[n];
				if(vo.name == name)
					return true;
				n++;
			}
			return false;
		}
		
		override public function setData( data:Object ):void{
			super.data = data;
			sendNotification( FLARE_DATA_POPULATED, getData() );
		}
		
		public function aquire():void{
			var loader:URLLoader = new URLLoader();
			loader.addEventListener(Event.COMPLETE, onLoadComplete);
			loader.load(new URLRequest(url));
		}
		
		private function onLoadComplete(event:Event):void{
			var a:Array,
				loader:URLLoader,
				d:Data;
			loader = URLLoader( event.target );
			a = JSON.decode(loader.data as String) as Array;
			stateMap = parseData(a)
			update();
		}
		public static function parseData(tuples:Array):Array{
			var a:Array,
				state:StateVO,
				o:Object;
			a = [];
			for each (o in tuples) {
				a.push( new StateVO ( o ) );
			}
			return a;
		}
		
		public function buildData(tuples:Array):Data
		{
			var fdata:Data = new Data();
			var tree:Tree = new Tree();
			map ={};
			
			tree.root = fdata.addNode({name:"root", size:0});
			map.root = tree.root;
			
			var t:StateVO, u:NodeSprite, v:NodeSprite;
			var p:String, pp:String, i:uint;
			
			// build data set and tree edges
			tuples.sortOn("fullPath");
			for each (t in tuples) {
				addTuple(t, fdata, tree)
			}
			
			actionMap = createActions(tuples, fdata);
			
			
			
			
			for each (u in tree.nodes) {
				u.sortEdgesBy(NodeSprite.CHILD_LINKS, "target.data.order");
			}
			
			fdata.tree = tree;
			return fdata;
		}
		
		private function createActions(tuples:Array, fdata:Data):Array{
			// create graph links
			var t:StateVO,
				a:Array,
				sourceNode:NodeSprite,
				targetNode:NodeSprite,
				action:ActionVO,
				actionEdge:EdgeSprite;
			a=[];
			for each (t in tuples) {
				sourceNode = map[t.fullPath];
				for each ( action in t.targets) {
					targetNode = map[action.target];
					if (targetNode){
						actionEdge = fdata.addEdgeFor(sourceNode, targetNode, true);
						actionEdge.data = action;
						a.push( action );
					} 
					else trace ("Missing node: "+action.name);
				}
			}
			return a;
		}
		
		private function addTuple(t:StateVO, fdata:Data, tree:Tree):void{
			var u:NodeSprite;
			var path:Array, p:String;
			
			p = addGroupToTree(fdata, tree, t.pkgArray);
				
			u = fdata.addNode(t);
			tree.addChild(map[p], u);
			map[t.fullPath] = u;
		}
		
		private function addGroupToTree(fdata:Data, tree:Tree, path:Array):String{
			var u:NodeSprite;
			var p:String, pp:String, i:uint;
			for (i=0, p=""; i<path.length; ++i) {
					pp = p;
					p += (i?".":"") + path[i];
					if (!map[p]) {
						u = fdata.addNode({name:p, size:0});
						tree.addChild(map[pp], u);
						map[p] = u;
					}
				}
				return p
			}
			
	}
	
	
}